package sketch.specs;

import java.util.Collection;
import java.util.LinkedList;
import java.util.List;

import org.eclipse.jdt.core.dom.Block;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
import org.eclipse.jdt.core.dom.VariableDeclarationStatement;
import org.eclipse.jdt.internal.core.dom.NaiveASTFlattener;

import sketch.ast.ASTUtils;
import sketch.main.SketchTestOptions;
import sketch.util.Checker;

public class UnusedVarDeclarationRemover {
	
	public final Block block;
	
	public final boolean recursively;
	
	public UnusedVarDeclarationRemover(Block block) {
		this(block, false);
	}
	
	public UnusedVarDeclarationRemover(Block block, boolean recursively) {
		Checker.checkNull(block, "The block input could not be null.");
		this.block = block;
		this.recursively = recursively;
	}
	
	public List<VariableDeclarationStatement> getAllRemovableVarDeclarationStatements() {
		List<VariableDeclarationStatement> removable = new LinkedList<VariableDeclarationStatement>();
		
		//get all declarations in this block
		List<VariableDeclarationStatement> allDecls = null;
		if(!this.recursively) {
		    allDecls = ASTUtils.getVariableDeclarations(block);
		} else {
			allDecls = new LinkedList<VariableDeclarationStatement>(ASTUtils.getVariableDeclarationsRecursively(block));
		}
		Checker.checkNull(allDecls, "The declarations should not be null!");
		//the number of declarations
//		System.out.println("In block" + block + " The num of declarations: " + allDecls.size()
//				+ ", use recursive? :" + this.recursively);
		//check is there any declarations removable
		for(VariableDeclarationStatement decl : allDecls) {
			//XXXTODO
			if(decl.toString().indexOf("Values") == -1) {
				continue;
			}
			UnusedVariableVerifier verifier = new UnusedVariableVerifier(decl);
			Block scopeBlock = ASTUtils.closeBlock(decl);
			//System.out.println("The scope block of decl: " + decl + "  is: " + scopeBlock);
			Checker.checkNull(scopeBlock, "The closest block for statement: " + decl + " could not be null!");
			verifier.visit(scopeBlock);
			if(verifier.isRemovable()) {
				removable.add(decl);
			}
		}
		
		return removable;
	}
	
	/**
	 * The visitor which finds out unused variable definitions
	 * */
	private static class UnusedVariableVerifier extends NaiveASTFlattener{
		private VariableDeclarationStatement statement; 
		private boolean couldRemove = true;
		private List<SimpleName> declaredNames = new LinkedList<SimpleName>();
		
		public UnusedVariableVerifier(VariableDeclarationStatement statement) {
			this.statement = statement;
			//System.out.println("parent of: " + statement + " is type: " + this.statement.getParent().getClass());
			List<VariableDeclarationFragment> fragments = statement.fragments();
			for(VariableDeclarationFragment fragment : fragments) {
				SimpleName name = fragment.getName();
				this.declaredNames.add(name);
			}
		}
		
//		@Override
//		public boolean visit(VariableDeclarationExpression expr) {
//			System.err.println(expr);
//			return super.visit(expr);
//		}
		
		//XXX a naive way, not sure it works or not
		//it may have some problem like:
		//  {
		//    int k = 10;
		//  }
		//  int k = 10
		//the cost could be reduced here!
		@Override
		public boolean visit(SimpleName name) {
			if(!couldRemove) {
				return false;
			}
			
			for(SimpleName simpleName : declaredNames) {
				if(simpleName.toString().equals(name.toString()) && simpleName.getStartPosition() != name.getStartPosition()) {
					//first try to where did this name come from
					VariableDeclarationStatement declaredStatement = this.findDeclaredStatement(name);
					if(SketchTestOptions.verbose) {
					    System.out.println("\n\n\ndeclared statement of name: " + name + " is: " + declaredStatement);
					}
					Checker.checkNull(declaredStatement, "The declared statement could not be null.");
					if(declaredStatement.toString().equals(this.statement.toString())
							&& declaredStatement.getStartPosition() == this.statement.getStartPosition()
							&& name.getStartPosition() > this.statement.getStartPosition() /*XXX this is not robust*/) {
						couldRemove = false;
						break;
					}
					//else we skip
				}
			}
			return super.visit(name);
		}
		
		private VariableDeclarationStatement findDeclaredStatement(SimpleName name) {
			Block closestParent = ASTUtils.closeBlock(name);
			VariableDeclarationStatement varDecl = null;
			while(closestParent != null) {
				 List<VariableDeclarationStatement> varDeclStatements = ASTUtils.getVariableDeclarations(closestParent);
				for(VariableDeclarationStatement varDeclStatement : varDeclStatements) {
					boolean found = false;
					Collection<SimpleName> declnames = ASTUtils.getDeclaredVariableNames(varDeclStatement);
					for(SimpleName declname : declnames) {
						if(declname.toString().equals(name.toString())) {
							found = true;
							break;
						}
					}
					if(found) {
						varDecl = varDeclStatement;
						break;
					}
				}
				//find it
				if(varDecl != null) {
					break;
				}
				closestParent = ASTUtils.closeBlock(closestParent);
			}
			
			return varDecl;
		}
		
		public boolean isRemovable() {
			//must call visit or accept first before getting result
			return this.couldRemove;
		}
	}
	
}